package ca.polymtl.rubikcube.CubeActivity;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Log;
import ca.polymtl.rubikcube.R;
import ca.polymtl.rubikcube.CubeActivity.model.Cubelet;
import ca.polymtl.rubikcube.CubeActivity.model.Side;
import ca.polymtl.rubikcube.util.Color;

public class CubeletRenderer {
	private static int FLOATSIZE = Float.SIZE / 8;
	private static int BYTESIZE = Byte.SIZE / 8;
	private float dim = 1f; // The dimension of a cubelet

	FloatBuffer[] _coords = new FloatBuffer[6];
	ByteBuffer _indices;
	FloatBuffer _textureCoords; 

	
	
	float[] matAmbient = { 0f, 0f, 0f, 0f };
	float[] matDiffuse = { 1f, 1f, 1f, 1f };
	float[] matSpecular = { 0f, 0f, 0f, 0f };

	private HashMap<Color, Integer> textures = new HashMap<Color, Integer>();

	private FloatBuffer generateCoordsBuffer(float[] coords) {
		FloatBuffer coordsBuffer;
		
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(coords.length * FLOATSIZE);
		byteBuffer.order(ByteOrder.nativeOrder());
		coordsBuffer = byteBuffer.asFloatBuffer();
		
		coordsBuffer.put(coords);
		coordsBuffer.position(0);
		
		return coordsBuffer;
	}
	
	public CubeletRenderer(GL10 gl) {
		float half_dim = dim / 2;
		float pos = half_dim;
		float neg = -half_dim;
		/**
		 * 
		 *      | Y
		 *      |
		 *      |
		 *      |
		 *      |
		 *      |_____________ X
		 *     /
		 *   /
		 *  /
		 * / Z
		 * 
		 */

		/*
		 * Indices:
		 *    0           3
		 *     +---------+
		 *     |         |
		 *     |         |
		 *     |         |
		 *     |         |
		 *     +---------+
		 *    1           2
		 * 
		 * The two triangles we draw are 0-1-3 and 1-2-3
		 * 
		 * Texture mapping:
		 *    0,0         1,0
		 *     +---------+
		 *     |         |
		 *     |         |
		 *     |         |
		 *     |         |
		 *     +---------+
		 *    0,1         1,1
		 *     
		 * The index-texture coord mapping is:
		 * - 0 = 0,0
		 * - 1 = 0,1
		 * - 2 = 1,1
		 * - 3 = 1,0
		 */
		
		_coords[Side.FRONT.ordinal()] = generateCoordsBuffer(new float[] {
				neg, pos, pos,
				neg, neg, pos,
				pos, neg, pos,
				pos, pos, pos
				});
		
		_coords[Side.UP.ordinal()] = generateCoordsBuffer(new float[] {
				pos, pos, neg,
				neg, pos, neg,
				neg, pos, pos,
				pos, pos, pos
				});
		
		_coords[Side.RIGHT.ordinal()] = generateCoordsBuffer(new float[] {
				pos, pos, neg,
				pos, pos, pos,
				pos, neg, pos,
				pos, neg, neg
				});
		
		_coords[Side.BACK.ordinal()] = generateCoordsBuffer(new float[] {
				neg, neg, neg,
				neg, pos, neg,
				pos, pos, neg,
				pos, neg, neg
				});
		
		_coords[Side.DOWN.ordinal()] = generateCoordsBuffer(new float[] {
				neg, neg, neg,
				pos, neg, neg,
				pos, neg, pos,
				neg, neg, pos
				});
		
		_coords[Side.LEFT.ordinal()] = generateCoordsBuffer(new float[] {
				neg, pos, pos,
				neg, pos, neg,
				neg, neg, neg,
				neg, neg, pos
				});

		byte[] indices = {
				0, 1, 3, // First triangle
				1, 2, 3 // Second triangle
		};

		ByteBuffer indicesByteBuffer = ByteBuffer.allocateDirect(indices.length * BYTESIZE);
		indicesByteBuffer.order(ByteOrder.nativeOrder());
		_indices = indicesByteBuffer;
		_indices.put(indices);
		_indices.position(0);

		float textureCoordinates[] = {
				0.0f, 0.0f, // 0
				0.0f, 1.0f, // 1
				1.0f, 1.0f, // 2
				1.0f, 0.0f, // 3
				};
		
		ByteBuffer textureCoordsByteBuffer = ByteBuffer.allocateDirect(textureCoordinates.length * 4);
		textureCoordsByteBuffer.order(ByteOrder.nativeOrder());
		_textureCoords = textureCoordsByteBuffer.asFloatBuffer();
		_textureCoords.put(textureCoordinates);
		_textureCoords.position(0);
	}
	
	public void loadGLTexture(GL10 gl, Context context) {
		// Load texture files		
		int bmpResourcesId[] = {
				R.drawable.tex_white,
				R.drawable.tex_blue,
				R.drawable.tex_red,
				R.drawable.tex_yellow,
				R.drawable.tex_green, 
				R.drawable.tex_orange,
				R.drawable.tex_black
		}; 
		
		// Allocate 7 texture ids
		int[] textureIds = new int[7];
		gl.glGenTextures(7, textureIds, 0);
		 
		for (int i = 0; i < 7; i++) {
			// Load the BMP
			Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), bmpResourcesId[i]);
			
			// Bind the texture
			gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIds[i]);
			 
			// Set some stuff
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
			
			if (bmp != null) {
				// Load the texture from the BMP
				GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
			}
			// Associate the color to the texture id
			textures.put(Color.getColorById(i), textureIds[i]);
			
			if (bmp != null) {
				bmp.recycle();
			}
		}	
	}

	/**
	 * Renders a {@link Cubelet} with OpenGL.
	 * 
	 * @param gl
	 *            The OpenGL context.
	 * @param cubelet
	 *            The cubelet to render.
	 */
	public void render(GL10 gl, Cubelet cubelet, boolean picking,  boolean pickingSide) {
		drawFace(gl, cubelet, Side.FRONT, picking, pickingSide);
		drawFace(gl, cubelet, Side.UP, picking, pickingSide);
		drawFace(gl, cubelet, Side.RIGHT, picking, pickingSide);
		drawFace(gl, cubelet, Side.BACK, picking, pickingSide);
		drawFace(gl, cubelet, Side.DOWN, picking, pickingSide);
		drawFace(gl, cubelet, Side.LEFT, picking, pickingSide);
	}

	/**
	 * Draws a face of the cubelet using two triangles.
	 * 
	 * @param position
	 *            The position in the _coords buffer.
	 */
	private void drawFace(GL10 gl, Cubelet cubelet, Side side, boolean picking, boolean pickingSide) {
		if (cubelet == null)
			return;

		Color c = cubelet.getSideColor(side);

		if (c == null)
			return;
		/*
		 * gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_AMBIENT, matAmbient, 0); gl.glMaterialfv(GL10.GL_FRONT,
		 * GL10.GL_DIFFUSE, matDiffuse, 0); gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_SPECULAR, matSpecular, 0);
		 */

		switch (side) {
		case BACK:
			gl.glNormal3f(0f, 0f, -1f);
			break;
		case DOWN: 
			gl.glNormal3f(0f, -1f, 0f);
			break;
		case FRONT:
			gl.glNormal3f(0f, 0f, 1f);
			break;
		case LEFT:
			gl.glNormal3f(-1f, 0f, 0f);
			break;
		case RIGHT:
			gl.glNormal3f(1f, 0f, 0f);
			break; 
		case UP:
			gl.glNormal3f(0f, 1f, 0f);
			break;
		}
		
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _coords[side.ordinal()]);
		if (picking) {
			gl.glDisable(GL10.GL_TEXTURE_2D);
			int red = 0, green = 0, blue = 0;
			if (pickingSide) {
				if (side == Side.FRONT) {
					red = 255; green = 255; blue = 0;
				}
				else if (side == Side.BACK) {
					red = 255; green = 0; blue = 0;
				}
				else if (side == Side.UP) {
					red = 0; green = 255; blue = 255;
				}
				else if (side == Side.DOWN) {
					red = 0; green = 255; blue = 0;
				}
				else if (side == Side.RIGHT) {
					red = 255; green = 0; blue = 255;
				}
				else if (side == Side.LEFT) {
					red = 0; green = 0; blue = 255;
				}
			}
			else {
				red = (cubelet.pos.getX() * 80) + 40;
				green = (cubelet.pos.getY() * 80) + 40;
				blue = (cubelet.pos.getZ() * 80) + 40;
			}
			gl.glColor4x(red << 8, green << 8, blue << 8, 1);
			gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_BYTE, _indices);
			gl.glColor4f(1f, 1f, 1f, 1);
			gl.glEnable(GL10.GL_TEXTURE_2D);
		} else {
			gl.glColor4f(1f, 1f, 1f, 1f);
			gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

			gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, _textureCoords);

			Integer col = textures.get(c);
			if (col == null) {
				Log.d("CUBE", "OUPPPPPPS");
			}
			gl.glBindTexture(GL10.GL_TEXTURE_2D,  (int)col);

			gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_BYTE, _indices);

			gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		}
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
	}

	public float getDimension() {
		return dim;
	}
}
